<template>
  <el-row class="testing-tab">
    <el-col :span="12" class="left">
      <el-tabs v-model="testTabName" class="test-tabs">
        <el-tab-pane class="test-tab" label="配置测试事件" name="first">
          <label v-if="testcases.length>0" class="my-label" for="testcase-select">选择测试事件:</label>
          <select
            v-if="testcases.length>0"
            id="testcase-select"
            v-model="editTestcase"
            name="testcase"
            @change="selectChange"
          >
            <!-- <option disabled value="">--select a testcase--</option> -->
            <option v-for="testcase in testcases" :key="testcase.name" :value="testcase.name">{{ testcase.name }}
            </option>
          </select>
          <el-row v-if="!visible" class="el-row-button">
            <el-col :span="24">
              <div class="button-wrapper" style="display: flex; justify-content: flex-start; align-items: center;">
                <el-button
                  v-if="!visible"
                  size="small"
                  type="primary"
                  @click="showInput"
                >
                  新建测试事件
                </el-button>
                <el-button
                  v-if="!visible && editTestcase"
                  size="small"
                  type="primary"
                  class="ml-10"
                  @click="confirmDeleteTestcase"
                >
                  删除当前测试事件
                </el-button>
              </div>
            </el-col>
          </el-row>
          <el-row v-if="visible" class="el-row-button">
            <el-col :span="24">
              <div class="button-wrapper" style="display: flex; justify-content: space-between; align-items: center;">
                <input
                  v-show="visible"
                  v-model="inputValue"
                  placeholder="请为新测试事件命名"
                  type="text"
                  style="width: 80%;"
                  @keydown.enter="handleKeyPress"
                >
                <el-button v-if="visible" size="small" type="primary" class="ml-10" @click="createTestcase">
                  确定
                </el-button>
                <el-button v-if="visible" size="small" type="primary" class="ml-10" @click="cancel">
                  取消
                </el-button>
              </div>
            </el-col>
          </el-row>
          <div class="editor-tab">
            <editor
              v-model="editContent"
              :options="editorOptions"
              class="editor"
              lang="json"
              theme="chrome"
              width="100%"
              @init="editorInit1"
            />
          </div>
          <span class=" button-wrapper">
            <el-button
              v-if="editContent && editTestcase"
              :disabled="isTesting"
              :loading="isTesting"
              size="small"
              type="primary"
              @click="testByJson"
            >
              开始测试
            </el-button>
          </span>
        </el-tab-pane>
        <el-tab-pane class="test-tab" label="编写代码测试" name="second">
          <div class="editor-tab">
            <editor
              v-model="testingFile.editContent"
              :options="editorOptions"
              class="editor"
              lang="python"
              theme="chrome"
              width="100%"
              @init="editorInit2"
            />
          </div>
          <span class=" button-wrapper">
            <el-button
              :disabled="isTesting"
              :loading="isTesting"
              size="small"
              type="primary"
              @click="testByCode"
            >
              开始测试
            </el-button>
          </span>
        </el-tab-pane>
      </el-tabs>
    </el-col>
    <el-col :span="12" class="right">
      <div v-if="executed" class="mt-5 mb-5">
        <el-alert
          :title="alertInfo"
          :type="alertType"
          show-icon
        />
        <el-button
          v-if="dependencyError"
          class="dependency-alert"
          :style="{ backgroundColor: '#FEF0F0', color: '#F56C6C', width: '100%', height: '37px' }"
          @click="gotoDependencyTab"
        >
          依赖未找到！点击此处安装依赖
        </el-button>
        <el-button
          v-if="handlerError"
          class="dependency-alert"
          :style="{ backgroundColor: '#FEF0F0', color: '#F56C6C', width: '100%', height: '37px' }"
          @click="gotoMainTab"
        >
          函数未部署！点击此处部署函数
        </el-button>
      </div>
      <el-tabs v-model="activeTabName">
        <el-tab-pane :disabled="!executed" label="返回结果" name="first">
          <pre v-if="executed" class="pre-viewer">{{ result }}</pre>
        </el-tab-pane>
        <el-tab-pane :disabled="!executed" label="日志输出" name="second">
          <pre v-if="executed" class="pre-viewer">{{ testLog }}</pre>
        </el-tab-pane>
        <el-tab-pane :disabled="!executed" label="请求信息" name="third">
          <pre v-if="executed" class="pre-viewer">{{ requestInfo }}</pre>
        </el-tab-pane>
      </el-tabs>
    </el-col>
  </el-row>
</template>

<script>
import editor from 'vue2-ace-editor';
import { mapActions, mapMutations, mapState } from 'vuex';
import { parseErrorMessage } from '@/utils/translate';
import { libraryFolderName } from '@/config';
import { getObjectUrl } from '@/utils/aliOss';
import { downloadOss } from '@/api/userProject';
import { parseParameters } from '@/utils/util';

export default {
  name: 'TestingTab',
  components: {
    editor
  },
  data() {
    return {
      result: 'null',
      testLog: 'null',
      drawer: false,
      testTabName: 'first',
      activeTabName: 'first',
      // requestInfo: JSON.stringify({ key1: 1, key2: 2, key3: 4 }, undefined, 4),
      requestInfo: 'null',
      isTesting: false,
      executed: false,
      alertType: 'success',
      alertInfo: '执行成功！',
      dependencyError: false,
      handlerError: false,
      editorOptions: {
        // 设置代码编辑器的样式
        enableBasicAutocompletion: true,
        enableSnippets: true,
        enableLiveAutocompletion: true,
        tabSize: 4,
        fontSize: 14,
        showPrintMargin: false // 去除编辑器里的竖线
      },
      initialValue: '',
      defaultValue: '',
      // editValue: JSON.stringify(JSON.parse('{ "key1": 1, "key2": 2 }'), undefined, 4),
      visible: false,
      inputValue: '',
      editTestcase: '',
      preTestcase: '',
      editContent: ''
    };
  },
  computed: {
    ...mapState('functions', ['testcases', 'templateFiles', 'isUploadTemplateFile', 'serviceName', 'functionName']),
    testingFile() {
      return this.templateFiles.testing;
    }
  },
  mounted() {
    document.addEventListener('keydown', this.saveEvent);
    this.initDefaultValue();
  },
  destroyed() {
    document.removeEventListener('keydown', this.saveEvent);
  },
  methods: {
    ...mapActions('functions', ['buildTemplateFile', 'buildTestcase', 'deleteFile']),
    ...mapMutations('functions', ['setActiveTab']),
    async testByJson() {
      const testcase = this.editContent;
      // 先自动保存测试事件的 .json 文件
      try {
        // 先在本地的 state 中保存一下
        this.testcases.forEach((item) => {
          if (item.name === this.editTestcase) {
            item.content = this.editContent;
            return;
          }
        });
        // 上传到 OSS
        const data = {
          name: this.editTestcase,
          content: this.editContent
        };
        await this.buildTestcase(data);
      } catch (err) {
        console.error(err);
      }
      this.requestInfo = testcase;
      this.testing(testcase, 'json');
    },
    async testByCode() {
      const store = this.$store.state.functions;
      const file = store.templateFiles.testing;
      // 测试的时候需要自动保存 testing.py文件
      if (file && file.editText !== file.editContent) {
        if (store.isUploadFile[file.id]) {
          this.$message.error('文件正在部署中...');
          return;
        }
        try {
          await this.buildTemplateFile(this.testingFile);
        } catch (err) {
          console.error(err);
        }
      }
      this.testing(file.editContent, 'code');
    },
    testing(text, method) {
      const store = this.$store.state.functions;
      this.$message({
        type: 'info',
        message: '正在测试函数'
      });
      this.isTesting = true;
      this.dependencyError = false;
      this.handlerError = false;
      this.$store.dispatch('functions/testing', { text, method }).then((response) => {
        const replace = function(str) {
          str = str.replace(/</g, '\&lt');
          str = str.replace(/>/g, '\&gt');
          return str;
        };
        let log;
        this.activeTabName = 'first';
        const result = JSON.parse(response.rspString);
        if (result.errorMessage) {
          // 处理错误日志
          const fcErrorRegexp = /Function execution error: .+\. Path: .+\. Headers: .+/; // 这条日志代表错误的开始，后续的日志全部省掉。 TODO: 将逻辑放在后端
          const logEntries = response.logResult.split('\n');
          const index = logEntries.findIndex(line => line.match(fcErrorRegexp));
          log = logEntries.slice(0, index).join('\n');

          const { errorSource, errorLog } = parseErrorMessage(result, store.insertLines);
          const logObj = JSON.parse(errorLog);
          if (logObj?.errorType === 'ImportModuleError') {
            this.dependencyError = true;
          }
          if (logObj?.errorType === 'HandlerNotFound') {
            this.handlerError = true;
          }
          this.alertType = 'error';
          this.alertInfo = '执行失败！错误类型：' + errorSource;
          this.result = errorLog;
        } else {
          log = response.logResult;
          this.alertType = 'success';
          this.alertInfo = '执行成功！';
          this.result = response.rspString;
        }
        this.testLog = replace(log);
      }).catch((err) => {
        this.alertInfo = '执行失败！';
        this.alertType = 'error';
        console.error(err);
      }).finally(() => {
        this.isTesting = false;
        this.executed = true;
      });
    },
    editorInit1() {
      require('brace/ext/language_tools');
      require('brace/mode/json');
      require('brace/theme/chrome');
      require('brace/mode/less');
      require('brace/snippets/json');
    },
    editorInit2() {
      require('brace/ext/language_tools');
      require('brace/mode/python');
      require('brace/theme/chrome');
      require('brace/mode/less');
      require('brace/snippets/python');
    },
    saveEvent(e) { // TODO 重构 和mainTab下代码重复
      // js监听键盘ctrl+s快捷键保存
      if (
        e.keyCode === 83 &&
          (navigator.platform.match('Mac') ? e.metaKey : e.ctrlKey)
      ) {
        e.preventDefault();
        if (this.testTabName === 'second') { // 保存测试文件 testing.py
          if (this.isUploadTemplateFile[this.testingFile.id]) {
            this.$message.error('文件正在部署中...');
            return;
          }
          if (this.testingFile.editText !== this.testingFile.editContent) {
            this.buildTemplateFile(this.testingFile);
          }
        } else if (this.testTabName === 'first') { // 保存测试事件 .json
          try {
            // 先在本地的 state 中保存一下
            this.testcases.forEach((item) => {
              if (item.name === this.editTestcase) {
                item.content = this.editContent;
                return;
              }
            });
            // 上传到 OSS
            const data = {
              name: this.editTestcase,
              content: this.editContent
            };
            this.buildTestcase(data);
          } catch (err) {
            console.error(err);
          }
        }
      }
    },
    showInput() {
      this.visible = true;
      this.inputValue = '';
    },
    async createTestcase() {
      const casename = this.inputValue;
      var flag = false;
      this.testcases.forEach((item) => {
        if (item.name === casename) {
          flag = true;
          return;
        }
      });
      if (!casename) {
        flag = true;
      }
      if (flag) {
        // 报错，测试事件名字重复
        this.$message.error('测试事件名称重复或不符合规范，请更换！');
        return;
      }
      // 不重复
      await this.initDefaultValue();
      this.testcases.push({
        name: casename,
        content: this.defaultValue
      });
      const data = {
        name: casename,
        content: this.defaultValue
      };
      this.buildTestcase(data);
      this.visible = false;
      this.editTestcase = casename;
      if (this.preTestcase) {
        try {
          var flag1 = false;
          this.testcases.forEach((item) => {
            if (item.name === this.preTestcase) {
              flag1 = item.content === this.editContent;
              item.content = this.editContent;
              return;
            }
          });
          const data = {
            name: this.preTestcase,
            content: this.editContent
          };
          if (!flag1) {
            this.buildTestcase(data);
          }
        } catch (err) {
          console.error(err);
        }
      }
      this.editContent = this.defaultValue;
      this.preTestcase = this.editTestcase;
    },
    selectChange(e) {
      // 切换测试事件的时候把当前测试事件保存
      if (this.preTestcase) {
        try {
          var flag = false;
          // 先在本地的 state 中保存一下
          this.testcases.forEach((item) => {
            if (item.name === this.preTestcase) {
              flag = item.content === this.editContent;
              item.content = this.editContent;
              return;
            }
          });
          // 上传到 OSS
          const data = {
            name: this.preTestcase,
            content: this.editContent
          };
          if (!flag) {
            this.buildTestcase(data);
          }
        } catch (err) {
          console.error(err);
        }
      }
      this.testcases.forEach((item) => {
        if (item.name === e.target.value) {
          this.editContent = item.content;
          return;
        }
      });
      this.preTestcase = this.editTestcase;
    },
    async initDefaultValue() {
      const vm = this; // 保存对 Vue 组件实例的引用
      const name = `${libraryFolderName}/${this.serviceName}/${this.functionName}/${this.functionName}.py`;
      const url = await getObjectUrl(name);
      const resp = await downloadOss(url);
      const func = this.functionName;
      const promise = resp.text().then(function(content) {
        var funcInfo = parseParameters(func, content);

        // 构建 JSON 对象
        const jsonObj = {};
        funcInfo['variables'].forEach(param => {
          const paramName = param.name;
          if (param.type === 'int') {
            jsonObj[paramName] = 0;
          } else if (param.type === 'float') {
            jsonObj[paramName] = 0.0;
          } else if (param.type === 'bool') {
            jsonObj[paramName] = true;
          } else if (param.type === 'dict') {
            jsonObj[paramName] = {};
          } else if (param.type === 'list' || param.type === 'set' || param.type === 'tuple') {
            jsonObj[paramName] = [];
          } else {
            jsonObj[paramName] = '';
          }
        });

        // 将 JSON 对象转换为 JSON 字符串
        const jsonStr = JSON.stringify(jsonObj, undefined, 4);

        // 保存默认测试用例到 Vue 组件实例的数据中
        vm.$set(vm, 'defaultValue', jsonStr);
        // console.log('execute', jsonStr);

        return Promise.resolve();
      });
      return promise;
    },
    gotoDependencyTab() {
      this.dependencyError = false;
      this.setActiveTab('third');
    },
    gotoMainTab() {
      this.handlerError = false;
      this.setActiveTab('first');
    },
    cancel() {
      this.visible = false;
    },
    // 在输入框中按下 enter 键
    handleKeyPress(event) {
      if (event.keyCode === 13) {
        this.createTestcase();
      }
    },
    // 删除测试事件
    deleteTestcase() {
      if (this.editTestcase === '') {
        return;
      }
      this.deleteFile(this.editTestcase);
      // this.testcases.filter((item) => {
      //   item.name !== this.editTestcase;
      // });
      for (let i = 0; i < this.testcases.length; i++) {
        if (this.testcases[i].name === this.editTestcase) {
          this.testcases.splice(i, 1);
        }
      }
      if (this.testcases.length === 0) {
        this.editTestcase = '';
        this.editContent = '';
        this.preTestcase = '';
      } else {
        this.editTestcase = this.testcases[0].name;
        this.editContent = this.testcases[0].content;
        this.preTestcase = this.editTestcase;
      }
    },
    async confirmDeleteTestcase() {
      try {
        await this.$confirm('确定要删除当前测试事件吗？', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        });
        this.deleteTestcase();
      } catch (err) {
        // 用户点击了取消按钮，不执行删除操作
      }
    }
  }
};
</script>

<style lang="scss">
.testing-tab {
  height: 100%;
  display: flex;
  border: 1px solid #d5d5d5;

  .left {
    padding-right: 10px;
    padding-left: 10px;
    height: 100%;
    border-right: 1px solid #d5d5d5;

    .test-tabs {
      overflow: auto;
      height: 100%;
      display: flex;
      flex-direction: column;

      .editor-tab {
        width: 100%;
        flex: 1;

        .editor {
          border-top: 1px solid #d5d5d5;
          border-bottom: 1px solid #d5d5d5;
          border-right: 1px solid #d5d5d5;
        }

      }
      .test-tab {
        display: flex;
        flex-direction: column;
        height: 100%;

        .button-wrapper {
          display: flex;
          margin: 8px;
        }
      }

    }

    .el-row-button {
      margin-bottom: 0%;
    }
  }

  .right {
    padding-left: 10px;
    padding-right: 10px;
    height: 100%;

    .el-tabs.el-tabs--top {
      height: calc(100% - 46px);
    }

    .el-tabs__content {
      height: calc(100% - 55px);
    }

    .el-tab-pane {
      height: 100%;
    }

    .pre-viewer {
      max-height: 100%;
      background-color: #263238;
      white-space: pre-wrap;
      line-height: 18px;
      font-family: Consolas, Menlo, Courier, monospace, -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif;
      font-size: 14px;
      font-variant-ligatures: contextual;
      -webkit-font-smoothing: antialiased;
      color: #eff;
      padding: 10px;
      overflow: auto;
    }
  }
}

</style>
<style scoped>
button:disabled {
  cursor: not-allowed;
}

.my-label {
  margin-right: 5px;
}

.dependency-alert {
  margin-top: 7px;
  text-align: left;
  padding-left: 30px;
  font-size: 13.5px;
}

</style>
